<?php


namespace App\Service\PayApi\Callback;


use App\Model\Dao\MchInfoDao;
use App\Model\Dao\TradeOrderBaseDao;
use App\Model\Dao\TradeOrderDetailDao;
use App\Utils\CustomUtils;
use App\Utils\RedisUtils;
use App\Utils\SignUtils;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Utils\Arr;
use Swlib\SaberGM;

/**
 * Class MchPayCallbackNotifyService
 * 商户支付回调通知发送 
 *
 * @package App\Service\PayApi\Callback
 */
class MchPayCallbackNotifyService {

    /**
     * 重试倒计时
     */
    const RETRY_INTERVAL = [
        0 => 0,
        1 => 5,
        2 => 10,
        3 => 30,
        4 => 90,
        5 => 300,
    ];

    /**
     * 请求超时时间
     */
    const REQUEST_TIMEOUT = 5;

    /**
     * @var TradeOrderDetailDao
     * @Inject()
     */
    protected $TradeOrderDetailDao;

    /**
     * @var TradeOrderBaseDao
     * @Inject()
     */
    protected $TradeOrderBaseDao;

    /**
     * @var MchInfoDao
     * @Inject()
     */
    protected $MchInfoDao;

    public function handleMerchantNotifyQueue () {
        $timestamp = time();
        $list      = RedisUtils::redisResource()->zRangeByScore('MchPayCallbackWithTime', "0", (string) $timestamp);
        RedisUtils::redisResource()->zRemRangeByScore("MchPayCallbackWithTime", "0", (string) $timestamp);

        foreach ($list as $orderId) {
            $this->_handleMerchantNotify($orderId);
        }
    }

    /**
     * @param $orderDetail
     * @param $orderBase
     *
     * @return null
     */
    public function pushIntoNotifyQueue ($orderDetail, $orderBase) {
        return $this->_pushIntoNotifyQueue($orderDetail, $orderBase);
    }

    public function _handleMerchantNotify ($orderId) {

        $orderDetail = $this->TradeOrderDetailDao->get($orderId);
        $orderBase   = $this->TradeOrderBaseDao->getWhere(['orderId' => $orderId]);
        $mch         = $this->MchInfoDao->get($orderDetail['mchId']);

        $timestamp = time();
        $notifyUrl = $orderBase['mchNotifyUrl'];

        $payload = [
            'appId'            => $mch['appId'],
            'mchUrl'           => $orderDetail['mchUrl'],
            'timestamp'        => $timestamp,
            'tradeNo'          => $orderDetail['tradeNo'],
            'transStatus'      => $orderDetail['transStatus'],
            'mchTransCurrency' => $orderDetail['mchTransCurrency'],
            'mchTransAmount'   => $orderDetail['mchTransAmount'],
            'orderNo'          => $orderDetail['orderNo'],
            'attach'           => $orderDetail['attach'],
        ];

        $payload['sign'] = SignUtils::makeSign($payload, $mch['appSecret']);

        CustomUtils::xzxDebug(json_encode([
            'title'   => '向商户发送支付回调通知',
            'url'     => $notifyUrl,
            'payload' => $payload,
        ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

        try {
            $resultNotify = SaberGM::post($notifyUrl, $payload, ['timeout' => self::REQUEST_TIMEOUT])->getBody()->getContents();
            strlen($resultNotify) > 1000 && $resultNotify = substr($resultNotify, 0, 100);
        } catch (\Throwable $e) {
            $resultNotify = 'FAIL';
        }

        $update = [
            'mchNotifyTimes'    => $orderBase['mchNotifyTimes'] + 1,
            'mchNotifyStatus'   => $resultNotify == 'SUCCESS' ? 'SUCCESS' : 'FAIL',
            'mchNotifyLastTime' => $timestamp,
        ];

        // 更新通知次数和状态
        $this->TradeOrderBaseDao->update($orderBase['id'], $update);

        $orderBase = array_merge($orderBase, $update);

        if ($resultNotify != 'SUCCESS') {
            // 通知失败, 推入下一次通知
            $this->_pushIntoNotifyQueue($orderDetail, $orderBase);
        }
    }

    /**
     * 推入商户回调有序列表
     *
     * @param $orderDetail ?array 交易订单详情
     * @param $orderBase   ?array 交易订单跟踪
     *
     * @return null
     */
    protected function _pushIntoNotifyQueue ($orderDetail, $orderBase) {

        if (!empty($orderBase['mchNotifyUrl'])) {

            if ($orderBase['mchNotifyStatus'] != 'SUCCESS') {

                $tryTimes = Arr::get($orderBase, 'mchNotifyTimes', 0);

                if (Arr::exists(self::RETRY_INTERVAL, $tryTimes)) {
                    $interval = self::RETRY_INTERVAL[$tryTimes];
                    $payload  = $orderDetail['id'];
                    RedisUtils::redisResource()->zAdd('MchPayCallbackWithTime', time() + $interval, $payload);

                    CustomUtils::xzxDebug(sprintf('订单 %s 将在 %s(%ss后) 发送第 %s 次支付回调通知', ...[
                        $orderDetail['tradeNo'],
                        date('Y-m-d H:i:s', time() + $interval),
                        $interval,
                        $tryTimes,
                    ]));
                }
                else {
                    CustomUtils::xzxDebug(sprintf('订单 %s 超出支付回调通知次数', $orderDetail['tradeNo']));
                }
            }
            else {
                CustomUtils::xzxDebug(sprintf('订单 %s 处于通知成功状态', $orderDetail['tradeNo']));
            }
        }
        else {
            CustomUtils::xzxDebug(sprintf('订单 %s 没有回调通知地址', $orderDetail['tradeNo']));
        }

        return null;
    }
}
